package io.kimmking.gateway.ratelimiter;

import io.kimmking.gateway.config.RateLimteConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.Instant;
import java.util.*;

@Slf4j
//@EnableConfigurationProperties({RateLimteConfig.class})
public class MyRedisRateLimiter implements RateLimiterPerInterfacePath {

    private RateLimteConfig rateLimteConfig;

//    @Autowired
    private ReactiveStringRedisTemplate redisTemplate;

//    @Autowired
//    @Qualifier("myRedisScript")
    private RedisScript script;

    public final static String remainingHeader = "X-RateLimit-Remaining";
    public final static String replenishRateHeader = "X-RateLimit-Replenish-Rate";
    public final static String burstCapacityHeader = "X-RateLimit-Burst-Capacity";
    public final static String requestedTokensHeader = "X-RateLimit-Requested-Tokens";


    public MyRedisRateLimiter(RateLimteConfig rateLimteConfig,
                              ReactiveStringRedisTemplate redisTemplate,
                              RedisScript script){
        this.rateLimteConfig = rateLimteConfig;
        this.redisTemplate = redisTemplate;
        this.script = script;
    }

    private static List<String> getKeys(String id) {
        String prefix = "request_rate_limiter.{" + id;
        String tokenKey = prefix + "}.tokens";
        String timestampKey = prefix + "}.timestamp";
        return Arrays.asList(tokenKey, timestampKey);
    }


    @Override
    public Mono<Response> isAllowed(String routeId, String id, String requestPath) {
//        RedisRateLimiter.Config routeConfig = this.loadConfiguration(routeId);
        Optional<RateLimiterConfigEntity> configEntity = rateLimteConfig.getRateLimitConfigs().stream()
                .filter(rateLimiterConfigEntity -> rateLimiterConfigEntity.getRoute_id().equals(routeId))
                .filter(r -> r.getRateLimitUrls().contains(requestPath))
                .findFirst();
        if (!configEntity.isPresent()) {
            log.warn("该接口未配置限流规则信息，所以网关层未限速该接口");
//            return Mono.empty();
            return Mono.just(new Response(true, new HashMap()));
        }
        int replenishRate = configEntity.get().getReplenishRate();
        int burstCapacity = configEntity.get().getBurstCapacity();
        int requestedTokens = configEntity.get().getRequestedTokens();

        try {
            List<String> keys = getKeys(id);
            List<String> scriptArgs = Arrays.asList(replenishRate + "", burstCapacity + "", Instant.now().getEpochSecond() + "", requestedTokens + "");
            Flux<List<Long>> flux = this.redisTemplate.execute(this.script, keys, scriptArgs);
            return flux.onErrorResume((throwable) -> {
                log.error("Error calling rate limiter lua", throwable);

                return Flux.just(Arrays.asList(1L, -1L));
            }).reduce(new ArrayList(), (longs, l) -> {
                longs.addAll(l);
                return longs;
            }).map((results) -> {
                boolean allowed = (Long)results.get(0) == 1L;
                Long tokensLeft = (Long)results.get(1);
                Response response = new Response(allowed, this.getHeaders(replenishRate, burstCapacity, requestedTokens,tokensLeft));

                log.info("isAllowed's response: " + response);

                return response;
            });
        } catch (Exception var10) {
            MyRedisRateLimiter.log.error("Error determining if user allowed from redis", var10);
            return Mono.just(new Response(true, this.getHeaders(replenishRate, burstCapacity, requestedTokens, -1L)));
        }
    }

    public Map<String, String> getHeaders(Integer replenishRate, Integer burstCapacity, Integer requestedTokens, Long tokensLeft) {
        Map<String, String> headers = new HashMap();

        headers.put(remainingHeader, String.valueOf(tokensLeft));
        headers.put(replenishRateHeader, String.valueOf(replenishRate));
        headers.put(burstCapacityHeader, String.valueOf(burstCapacity));
        headers.put(requestedTokensHeader, String.valueOf(requestedTokens));

        return headers;
    }

    @Override
    public Map getConfig() {
        return null;
    }

    @Override
    public Class getConfigClass() {
        return null;
    }

    @Override
    public Object newConfig() {
        return null;
    }
}
